using System;
using System.Collections.Generic;
using System.Data.Linq.Mapping;
using PropertyExpression.Common;
using System.Linq;

namespace PropertyExpression.ReactiveRelation
{
    public static partial class Value
    {
        internal static IPropertyValue<TProperty> CreatePropertyValue<TEntity, TProperty>(
            this IEntityWrapper<TEntity> entityWrapper,
            IPropertyExpressionProxy<TEntity, TProperty> propertyExpressionProxy)
        {
            return new PropertyValue<TEntity, TProperty>(entityWrapper, propertyExpressionProxy);
        }

        private class PropertyValue<TEntity, TProperty> : IPropertyValue<TProperty>
        {
            private readonly IEntityWrapper<TEntity> entityWrapper;
            private readonly IPropertyExpressionProxy<TEntity, TProperty> propertyExpressionProxy;
            private readonly ReverseEntitiesProvider<TEntity, TProperty> reverseEntitiesProvider;

            public PropertyValue(IEntityWrapper<TEntity> entityWrapper, 
                IPropertyExpressionProxy<TEntity, TProperty> propertyExpressionProxy)
            {
                this.entityWrapper = entityWrapper;
                this.propertyExpressionProxy = propertyExpressionProxy;
                reverseEntitiesProvider = new ReverseEntitiesProvider<TEntity, TProperty>(
                    () => this.propertyExpressionProxy,
                    (reverseEntity, previousValueProcessor) =>
                    this.entityWrapper.GetReverseEntities(reverseEntity, previousValueProcessor)
                    );
            }

            public IEnumerable<object> GetReverseEntities(object sender, 
                IPreviousValueProcessor previousValueProcessor)
            {
                return reverseEntitiesProvider.GetReverseEntities(sender, previousValueProcessor);
            }

            public IEnumerable<object> GetReverseEntitiesForCollection(object sender, 
                IPreviousValueProcessor previousValueProcessor)
            {
                return reverseEntitiesProvider.GetReverseEntitiesForCollection(sender, previousValueProcessor);
            }

            public IEnumerable<ICell> GetReverseCells()
            {
                return GetCells(
                    () => new ReverseCellProvider(this),
                    cellProvider => new ReverseCell(this, cellProvider),
                    true,
                    associationAttribute => associationAttribute.OtherKey
                    );
            }

            public IOptionalValue<TResult> ProcessOneToManyCollectionNewItem<TResult>(
                Func<object, TResult> addEntityFunc,
                Func<object, TResult> removeEntityFunc,
                Func<TResult> notExistFunc, 
                IPropertySettor propertySettor,
                IOneToManyCollectionNewItemProcessor oneToManyCollectionNewItemProcessor)
            {
                return propertyExpressionProxy.ProcessOneToManyCollectionNewItem(
                    () => entityWrapper.Entity, oneToManyCollectionNewItemProcessor,
                    addEntityFunc, removeEntityFunc, notExistFunc).AsOptionalValue();
            }

            public IEnumerable<ICell> GetCells()
            {
                return GetCells(
                    () => new CellProvider(this),
                    cellProvider => new Cell(this, cellProvider),
                    false,
                    associationAttribute => associationAttribute.ThisKey
                    );
            }

            public IOptionalValue<TProperty> GetValue(IPropertySettor propertySettor, 
                IOneToManyCollectionNewItemProcessor oneToManyCollectionNewItemProcessor)
            {
                return new ValueInfo(propertySettor, this).GetValue().AsOptionalValue();
            }

            public IPropertyValue<TResultProperty> GetPropertyValue<TResultProperty>(
                IPropertyExpressionProxy<TProperty, TResultProperty> sunPropertyExpressionProxy)
            {
                return this.CreatePropertyValue(sunPropertyExpressionProxy);
            }

            private IEnumerable<ICell> GetCells(
                Func<ICellProvider> cellProviderFunc, 
                Func<ICellProvider, ICell> cellFunc, 
                bool checkForEntitySet, 
                Func<AssociationAttribute, string> keyNamesFunc)
            {
                return new[] {cellFunc(cellProviderFunc())}.Concat(
                    GetKeyCells(checkForEntitySet, keyNamesFunc, cellFunc)
                    );
            }

            private IEnumerable<ICell> GetKeyCells(bool checkForEntitySet,
                Func<AssociationAttribute, string> keyNamesFunc, Func<ICellProvider, ICell> cellFunc)
            {
                if (typeof(TProperty).IsEntitySet() == checkForEntitySet)
                {
                    //TODO: improve performance
                    var associationAttribute = propertyExpressionProxy.GetPropertyInfo()
                        .GetAssociationAttribute();
                    if (associationAttribute.HasValue())
                    {
                        foreach (var keyName in keyNamesFunc(associationAttribute.GetValue())
                            .Split(InternalUtil.Separator))
                        {
                            yield return cellFunc(new PKCellProvider(keyName));
                        }                        
                    }
                }
            }

            private class Cell : ICell
            {
                private readonly PropertyValue<TEntity, TProperty> parent;
                private readonly ICellProvider cellProvider;

                public Cell(PropertyValue<TEntity, TProperty> parent, ICellProvider cellProvider)
                {
                    this.parent = parent;
                    this.cellProvider = cellProvider;
                }

                public string PropertyName
                {
                    get
                    {
                        return cellProvider.PropertyName;
                    }
                }

                public string EntityFullName
                {
                    get
                    {
                        return typeof(TEntity).FullName;
                    }
                }

                public IEnumerable<object> GetReverseEntities(object sender, 
                    IPreviousValueProcessor previousValueProcessor)
                {
                    return parent.entityWrapper.GetReverseEntities(sender, previousValueProcessor);
                }
            }

            private class ReverseCell : ICell
            {
                private readonly PropertyValue<TEntity, TProperty> parent;
                private readonly ICellProvider cellProvider;
                private readonly string entityFullName;

                public ReverseCell(PropertyValue<TEntity, TProperty> parent, ICellProvider cellProvider)
                {
                    this.parent = parent;
                    this.cellProvider = cellProvider;
                    entityFullName = parent.propertyExpressionProxy
                        .GetReturnValueTypeGenericArgumentFullName();
                }

                public string PropertyName
                {
                    get { return cellProvider.PropertyName; }
                }

                public string EntityFullName
                {
                    get { return entityFullName; }
                }

                public IEnumerable<object> GetReverseEntities(object sender, 
                    IPreviousValueProcessor previousValueProcessor)
                {
                    return parent.GetReverseEntitiesForCollection(sender, previousValueProcessor);
                }
            }

            private interface ICellProvider
            {
                string PropertyName { get; }
            }

            private class CellProvider : ICellProvider
            {
                private readonly PropertyValue<TEntity, TProperty> parent;

                public CellProvider(PropertyValue<TEntity, TProperty> parent)
                {
                    this.parent = parent;
                }

                public string PropertyName
                {
                    get
                    {
                        return parent.propertyExpressionProxy.GetPropertyName();
                    }
                }
            }

            private class PKCellProvider : ICellProvider
            {
                private readonly string propertyName;

                public PKCellProvider(string propertyName)
                {
                    this.propertyName = propertyName;
                }

                public string PropertyName
                {
                    get
                    {
                        return propertyName;
                    }
                }
            }

            private class ReverseCellProvider : ICellProvider
            {
                public ReverseCellProvider(PropertyValue<TEntity, TProperty> parent)
                {
                    propertyName = parent.propertyExpressionProxy.GetReversePropertyInfoName();
                }

                private readonly string propertyName;
                public string PropertyName
                {
                    get { return propertyName; }
                }
            }

            private class ValueInfo : IValueInfo<TProperty, TEntity>
            {
                private readonly IPropertySettor propertySettor;
                private readonly PropertyValue<TEntity, TProperty> parent;

                public ValueInfo(IPropertySettor propertySettor,
                    PropertyValue<TEntity, TProperty> parent)
                {
                    this.propertySettor = propertySettor;
                    this.parent = parent;
                }

                public IPropertySettor PropertySettor
                {
                    get { return propertySettor; }
                }

                public IEntityPropertyProxy EntityPropertyProxy
                {
                    get { return new PropertyProxy(parent); }
                }

                public IPropertyExpressionProxy<TEntity, TProperty> PropertyExpressionProxy
                {
                    get { return parent.propertyExpressionProxy; }
                }

                public TEntity Entity
                {
                    get { return parent.entityWrapper.Entity; }
                }
            }

            private class PropertyProxy : IEntityPropertyProxy
            {
                private readonly PropertyValue<TEntity, TProperty> parent;

                public PropertyProxy(PropertyValue<TEntity, TProperty> parent)
                {
                    this.parent = parent;
                }

                public object Entity
                {
                    get { return parent.entityWrapper.Entity; }
                }

                public string PropertyName
                {
                    get { return parent.propertyExpressionProxy.GetPropertyName(); }
                }
            }
        }

        private class ReverseEntitiesProvider<TEntity, TProperty>
        {
            private readonly Func<IPropertyExpressionProxy<TEntity, TProperty>> propertyExpressionProxyFunc;
            private readonly Func<object, IPreviousValueProcessor, IEnumerable<object>> getReverseEntitiesFunc;

            public ReverseEntitiesProvider(
                Func<IPropertyExpressionProxy<TEntity, TProperty>> propertyExpressionProxyFunc,
                Func<object, IPreviousValueProcessor, IEnumerable<object>> getReverseEntitiesFunc)
            {
                this.propertyExpressionProxyFunc = propertyExpressionProxyFunc;
                this.getReverseEntitiesFunc = getReverseEntitiesFunc;
            }

            public IEnumerable<object> GetReverseEntities(
                object sender, IPreviousValueProcessor previousValueProcessor)
            {
                return PropagateReverseEntities(
                    propertyExpressionProxyFunc().GetReverseEntities(sender),
                    previousValueProcessor
                    );
            }

            private IEnumerable<object> PropagateReverseEntities(
                IEnumerable<object> reverseEntities, IPreviousValueProcessor previousValueProcessor)
            {
                return reverseEntities.SelectMany(
                    reverseEntity => getReverseEntitiesFunc(reverseEntity, previousValueProcessor)
                    );
            }

            public IEnumerable<object> GetReverseEntitiesForCollection(
                object sender, IPreviousValueProcessor previousValueProcessor)
            {
                return PropagateReverseEntities(
                    propertyExpressionProxyFunc().GetReversePropertyValue(sender, previousValueProcessor),
                    previousValueProcessor
                    );
            }
        }    
    }
}